#ifndef __STENCIL_H__
#define __STENCIL_H__
#include <stdio.h>
#include <malloc.h>
#include <iostream>

#include <cassert>
#include <array>

#include "tbb/tick_count.h"

#ifdef _DIST_
#include <cnc/dist_cnc.h>
#else
#include "cnc/cnc.h"
#endif

#include "cnc/default_tuner.h"


#include "definitions.h"

using namespace std;

// #define DEBUG 1

#include "cnc/debug.h"

class TileTag {
  array<int64_t, 8> m_vec;
  
public:
  TileTag() {}
  TileTag(int64_t t, int64_t i, int64_t j,
          int64_t T, int64_t M, int64_t N, int64_t P)
  {      
    m_vec[0] = t;
    m_vec[1] = i;
    m_vec[2] = j;
    m_vec[3] = T;
    m_vec[4] = M;
    m_vec[5] = N;
    m_vec[6] = P;
    // Dummy value for alignment
    m_vec[7] = 0xffffffff;
  }

  TileTag(const TileTag &tt) : m_vec(tt.m_vec) {}  

  bool operator==(const TileTag &tt) const {
    return m_vec[0] == tt[0] && 
      m_vec[1] == tt[1] && 
      m_vec[2] == tt[2];
  }

  operator size_t() const {
    return m_vec.size() * sizeof (int64_t);
  }

  int64_t operator[](int64_t i) const {
    return m_vec[i];
  }

  ostream& operator<< (ostream& stream) {  
    for (int64_t i=0; i<7; i++)
      stream << (*this)[i] << " ";
    
    return stream;
  }
};

template <>
size_t cnc_tag_hash_compare< TileTag >::hash(const TileTag& tt) const;
template <>
bool cnc_tag_hash_compare< TileTag >::equal(const TileTag& a, const TileTag& b) const;
std::ostream & cnc_format( std::ostream& os, const TileTag & t );
ostream& operator<< (ostream& stream, const TileTag &tt);

// forward declaration
class stencil_context;

// our stencil core
class stencil_step
{
  // Step local variables (also has persistence because of current implementation)
public:
  int64_t execute( const TileTag & t, stencil_context & c ) const;
};

class stencil_tuner : public CnC::step_tuner<>, public CnC::hashmap_tuner
{
public:
  stencil_tuner() {}
  
  // int64_t compute_on( const TileTag & t, stencil_context & ) const {
  //   // if ((t[1] + t[2]) % 16 < 8) return 0;
  //   // return 1;
  // }
  // int64_t get_count( const TileTag & tag ) const {return 3;}

  template< class DC >
  void depends( const TileTag & tag, stencil_context & c, DC & dc ) const;
};

class DoublePtrWrapper {
public:
  void * M;
  DoublePtrWrapper() : M(NULL) {}
  DoublePtrWrapper(void *ptr_M) : M(ptr_M){}
};

class stencil_context : public CnC::context< stencil_context > {
public:
  stencil_tuner m_tuner;
  CnC::item_collection< TileTag, DoublePtrWrapper, stencil_tuner > M;
  CnC::step_collection< stencil_step, stencil_tuner > m_steps;

  CnC::tag_collection< TileTag > m_tags;

  stencil_context() : 
    m_tuner(), M(*this, "M", m_tuner), m_tags(*this), m_steps(*this) {

    // prescription
    m_tags.prescribes( m_steps, *this );

    // producer/consumer
    m_steps.consumes( M );
    m_steps.produces( M );
  }

};

/* // ******************************************************************************************* */

template< class DC >
void stencil_tuner::depends( const TileTag & tt, stencil_context & c, DC & dc ) const
{
  assert(size_t(tt) > 0);

  int64_t t1 = tt[0];
  int64_t t2 = tt[1];
  int64_t t3 = tt[2];
  int64_t pT = tt[3];
  int64_t pM = tt[4];
  int64_t pN = tt[5];
  int64_t pP = tt[6];

  DoublePtrWrapper wA;

  // The condition is really: is the predecessor along each dimension
  // in the space or in the environment ?
  // If the condition is true, the predecessor is outside !
  bool condt1 = 
    (t1-1 < ceild(-pN-15,16)) ||
    (t2<max(t1-1,-(t1-1)-1 )) ||
    (t2 > min(min(floord(-8*(t1-1)+pT-2,8),floord(8*(t1-1)+pN+7,8)),floord(pT+pN-2,16))) ||
    (t3 < max(max(0,ceild((t1-1)+t2-1,2)),ceild(16*t2-pN-14,16))) || 
    (t3 >min(min(floord(pT+pN-2,16),floord(16*t2+pN+14,16)),floord(8*(t1-1)+8*t2+pN+15,16)));

  bool condt2 = 
    ((t2-1) < max(t1,-t1-1)) ||
    ((t2-1) > min(min(floord(-8*t1+pT-2,8),floord(8*t1+pN+7,8)),floord(pT+pN-2,16))) ||
    (t3 < max(max(0,ceild(t1+(t2-1)-1,2)),ceild(16*(t2-1)-pN-14,16))) ||
    (t3 > min(min(floord(pT+pN-2,16),floord(16*(t2-1)+pN+14,16)),floord(8*t1+8*(t2-1)+pN+15,16)));

  bool condt3 = 
    ((t3-1) < max(max(0,ceild(t1+t2-1,2)),ceild(16*t2-pN-14,16))) ||
    ((t3-1) > min(min(floord(pT+pN-2,16),floord(16*t2+pN+14,16)),floord(8*t1+8*t2+pN+15,16)));
  
  if (!condt1) {
    dc.depends(c.M, TileTag(t1-1,  t2,  t3,  pT,  pN,  pN,  pN));
  } 
  if (!condt2) {
    dc.depends(c.M, TileTag(t1,  t2-1,  t3,  pT,  pN,  pN,  pN));
  } 
  if (!condt3) {
    dc.depends(c.M, TileTag(t1,  t2,  t3-1,  pT,  pN,  pN,  pN));
  } 
}

// *******************************************************************************************

class Runnable {
public:
  virtual void operator()() {
    std::cerr << "Need derived class" << endl;
    assert(false);
  }
  friend ostream &operator<<(ostream &os, Runnable &);

protected:
  // Virtual friend idiom
  virtual void print(ostream &os) {
    std::cerr << "Need derived class" << endl;
    assert(false);    
  }
};

class Stencil : public Runnable {
  void *M;
  int64_t pT, pM, pN, pP;

public:
  Stencil(void *a, int64_t T, int64_t M, int64_t N, int64_t P) : 
    M(a), pT(T), pM(M), pN(N), pP(N) {}
  void operator()();

protected:
  virtual void print(ostream &os) {
    os << "CnC version";
  }
};

#endif
